#include <time.h>
#include "portal_http.h"
#include "portal_user.h"
#include "portal_pkt.h"
#include "portal_nas.h"
#include "portal_md5.h"

static char *portal_login_page = "<html>\r\n<head>\r\n<title>portal authentication page</title>\r\n</head>\r\n<body>\r\n<script type=\"text/javascript\">\r\nfunction postform() {\r\ndocument.getElementById(\'form1\').action = \"./login.html\" + location.search;\r\n"\
  "document.getElementById(\'form1\').submit();\r\n}\r\n</script>\r\n\n<form action=\"login.html\" method=\"post\" id=\"form1\">\r\n<p>username:<input type=\"text\" name=\"username\"/></p>\r\n"\
  "<p>password:<input type=\"text\" name=\"password\"/></p>\r\nbind mac<input type=\"checkbox\" name=\"bindmac\" value=\"1\"/><br>\r\n<input type=\"button\" value=\"login\" onclick=\"postform()\"/>\r\n</form>\r\n</body>\r\n</html>\r\n";

static char *portal_online_page = "<html>\r\n<head>\r\n<title>portal online page</title>\r\n</head>\r\n<body>\r\n<script type=\"text/javascript\">\r\n"\
"function postform() {\r\ndocument.getElementById('form1').action = \"./logout.html\" + location.search;\r\n"\
"document.getElementById('form1').submit();\r\n}\r\n</script>\r\n<form action=\"logout.html\" method=\"post\" id=\"form1\">\r\n"\
"<p>User online success!</p><p>Please press logout button to offline.</p>\r\n<input type=\"button\" value=\"logout\" onclick=\"postform()\"/>\r\n"
"</form>\r\n</body>\r\n</html>\r\n";

static char *portal_offline_page = "<html>\r\n<head>\r\n<title>portal offline page</title>\r\n</head>\r\n<body>\r\n<p>User offline success!</p>\r\n</body>\r\n</html>\r\n";

static char *http_head_buf = "HTTP/1.0 200 OK\r\nServer: zfw\r\n\r\n";

static char *http_bad_req = "HTTP/1.0 400 Bad Request\r\n\r\n";

char *portal_result_authen_failed = "HTTP/1.0 200 OK\r\nServer: zfw\r\n\r\n<p>authentication failed, reason %s</p>\r\n";

char *portal_result_302 =
    "HTTP/1.1 302 temporarily\r\n"\
    "Location: http://%s:%d/%s?id=%s\r\n"\
    "Cache-Control: no-cache\r\n"\
    "Server: zfw Server\r\n\r\n";

static struct portal_url_arg_map_s portal_uri_arg[] = {
    {"wlanuserip=", PORTAL_URL_USERIP},
    {"wlanacname=", PORTAL_URL_ACNAME},
    {"ssid=", PORTAL_URL_SSID},
    {"wlanusermac=", PORTAL_URL_USERMAC},
    {"wlanparameter=", PORTAL_URL_USERMAC},
    {"wlanapmac=", PORTAL_URL_APMAC},
    {"wlanuserfirsturl=", PORTAL_URL_FIRSTURL},
    {"winterface=", PORTAL_URL_WINTERFACE},
    {"nasip=", PORTAL_URL_ACIP},
    {"mac=", PORTAL_URL_USERMAC},
    {"url=", PORTAL_URL_FIRSTURL},
    {"t=", PORTAL_URL_ENCRYPT},
    {"username=", PORTAL_URL_USERNAME},
    {"password=", PORTAL_URL_PASSWORD}
};


static struct portal_user_s *portal_uri_args_parse(struct portal_fd_poll_s *fd_poll, char *uri)
{
    int i;
    char *arg_start;
    char *and_mark;
    char arg_buf[1024];
    IPADDRTYPE ipv4 = 0, nasip;
    IPADDRTYPE6 ipv6;
    uchar tmp[64] = {0}, usermac[64], mac_out[64], acname[65] = {0};
    char ip[64] = {0};
    int af;
    int ret;
    struct portal_uri_store_s uri_store[PORTAL_URL_MAX];
    struct portal_user_s *user;
    int idx;

    memset(uri_store, 0, sizeof(uri_store));
    for (i = 0; i < sizeof(portal_uri_arg) / sizeof(portal_uri_arg[0]); i++) {
        if (!(arg_start = strstr(uri, portal_uri_arg[i].arg_name))) {
            continue;
        }

        if (*(arg_start - 1) != '&' && *(arg_start - 1) != 0) {
            continue;
        }
        idx = portal_uri_arg[i].arg_type;
        uri_store[idx].arg_type = portal_uri_arg[i].arg_type;
        uri_store[idx].start = arg_start + strlen(portal_uri_arg[i].arg_name);
        and_mark = strstr(uri_store[idx].start, "&");
        if (and_mark) {
            uri_store[idx].len = and_mark - uri_store[idx].start;
        } else {
            uri_store[idx].len = strlen(uri_store[idx].start);
        }
    }

    memset(arg_buf, 0, sizeof(arg_buf));
    if (uri_store[PORTAL_URL_ENCRYPT].start
        && strncmp(uri_store[PORTAL_URL_ENCRYPT].start, "wireless-v2-plain", strlen("wireless-v2-plain"))) {
        if (uri_store[0].start) {
            memcpy(arg_buf, uri_store[0].start, uri_store[0].len);
            portal_str_to_hex(arg_buf, strlen(arg_buf), tmp);
            portal_dec_des_ncbc(tmp, (uchar *)ip, uri_store[0].len >> 1);
            PORTAL_DEBUG("ip %s", ip);
            if (strchr(ip, '.')) {
                af = AF_INET;
                ret = inet_pton(AF_INET, ip, &ipv4);
            } else {
                af = AF_INET6;
                ret = inet_pton(AF_INET6, ip, &ipv6);
            }
            if (!ret) {
                return NULL;
            }
            user = portal_find_user(ntohl(ipv4), &ipv6, af);
            if (!user) {
                user = malloc(sizeof(struct portal_user_s));
                if (!user) {
                    return NULL;
                }
                user->authen_mode = portal_args.authen_mode;
                user->ipv4 = ntohl(ipv4);
                PORTAL_DEBUG("add user %x", user->ipv4);
                memcpy(&user->ipv6, &ipv6, sizeof(IPADDRTYPE6));
                if (af == AF_INET) {
                    list_add_tail(&user->lst, &portal_args.v4_user);
                } else {
                    list_add_tail(&user->lst, &portal_args.v6_user);
                }
                user->af = af;
            }
            list_add_tail(&fd_poll->usr_lst, &user->sock_lst);
            user->sock_num++;
        } else {
            return NULL;
        }
        for (i = 1; i < PORTAL_URL_MAX; i++) {
            if (uri_store[i].start) {
                switch (uri_store[i].arg_type) {
                case PORTAL_URL_USERMAC:
                    memset(tmp, 0, sizeof(tmp));
                    memset(usermac, 0, sizeof(usermac));
                    memset(mac_out, 0, sizeof(mac_out));
                    memcpy(mac_out, uri_store[i].start, uri_store[i].len);
                    portal_str_to_hex((char *)mac_out, uri_store[i].len, tmp);
                    portal_dec_des_ncbc(tmp, usermac, 16);
                    sscanf((char *)usermac, "%02x%02x%02x%02x%02x%02x",
                           (unsigned int *)&user->mac[0], (unsigned int *)&user->mac[1],
                           (unsigned int *)&user->mac[2], (unsigned int *)&user->mac[3],
                           (unsigned int *)&user->mac[4], (unsigned int *)&user->mac[5]);
                    PORTAL_DEBUG("usermac %s", usermac);
                    break;
                case PORTAL_URL_APMAC:
                    break;
                case PORTAL_URL_FIRSTURL:
                    break;
                case PORTAL_URL_ACIP:
                    if (!(nasip = portal_find_acip_by_md5((uchar *)uri_store[i].start))
                        && !user->nasip) {
                        portal_del_user(user);
                        PORTAL_DEBUG("nasip no exist");
                        return NULL;
                    } else {
                        PORTAL_DEBUG("nasip %x", nasip);
                        if (!user->nasip) {
                            user->nasip = nasip;
                        }
                    }
                    break;
                case PORTAL_URL_ACNAME:
                    portal_str_to_hex(uri_store[i].start, uri_store[i].len, acname);
                    portal_dec_des_ncbc(acname, (uchar *)user->acname, uri_store[i].len >> 1);
                    if (!(nasip = portal_find_acip_by_name(user->acname))) {
                        portal_del_user(user);
                        PORTAL_DEBUG("here");
                        return NULL;
                    } else {
                        PORTAL_DEBUG("nasip %x", nasip);
                        if (!user->nasip) {
                            user->nasip = nasip;
                        }
                    }
                    break;
                default:
                    break;
                }
            }
        }
    } else {
        if (uri_store[0].start) {
            memcpy(arg_buf, uri_store[0].start, uri_store[0].len);
            if (strchr(arg_buf, '.')) {
                af = AF_INET;
                ret = inet_pton(AF_INET, arg_buf, &ipv4);
            } else {
                af = AF_INET6;
                ret = inet_pton(AF_INET6, arg_buf, &ipv6);
            }
            if (!ret) {
                return NULL;
            }
            user = portal_find_user(ntohl(ipv4), &ipv6, af);
            if (!user) {
                user = malloc(sizeof(struct portal_user_s));
                if (!user) {
                    return NULL;
                }
                user->authen_mode = portal_args.authen_mode;
                user->ipv4 = ntohl(ipv4);
                PORTAL_DEBUG("add user %x", user->ipv4);
                memcpy(&user->ipv6, &ipv6, sizeof(IPADDRTYPE6));
                if (af == AF_INET) {
                    list_add_tail(&user->lst, &portal_args.v4_user);
                } else {
                    list_add_tail(&user->lst, &portal_args.v6_user);
                }
                user->af = af;
            }
            list_add_tail(&fd_poll->usr_lst, &user->sock_lst);
            fd_poll->user = user;
            user->sock_num++;
        } else {
            return NULL;
        }
        for (i = 1; i < PORTAL_URL_MAX; i++) {
            if (uri_store[i].start) {
                switch (uri_store[i].arg_type) {
                case PORTAL_URL_USERMAC:
                    if (uri_store[PORTAL_URL_ENCRYPT].start) {
                        if (uri_store[i].len != 12) {
                            portal_del_user(user);
                            PORTAL_DEBUG("here,len %d, %s", uri_store[i].len, uri_store[i].start);
                            return NULL;
                        }
                        memcpy(mac_out, uri_store[i].start, uri_store[i].len);
                        portal_str_to_hex((char *)mac_out, uri_store[i].len, user->mac);
                        break;
                    } else if (uri_store[i].len != 48) {
                        portal_del_user(user);
                        PORTAL_DEBUG("here,len %d, %s", uri_store[i].len, uri_store[i].start);
                        return NULL;
                    }
                    memset(tmp, 0, sizeof(tmp));
                    memset(usermac, 0, sizeof(usermac));
                    memset(mac_out, 0, sizeof(mac_out));
                    memcpy(mac_out, uri_store[i].start, uri_store[i].len);
                    portal_str_to_hex((char *)mac_out, uri_store[i].len, tmp);
                    portal_dec_des_ecb(tmp, usermac, 24);
                    sscanf((char *)usermac, "%02X-%02X-%02X-%02X-%02X-%02X", (unsigned int *)&user->mac[0], (unsigned int *)&user->mac[1],
                           (unsigned int *)&user->mac[2], (unsigned int *)&user->mac[3], (unsigned int *)&user->mac[4], (unsigned int *)&user->mac[5]);
                    break;
                case PORTAL_URL_APMAC:
                    break;
                case PORTAL_URL_FIRSTURL:
                    break;
                case PORTAL_URL_WINTERFACE:
                    break;
                case PORTAL_URL_ACIP:
                    memcpy(arg_buf, uri_store[i].start, uri_store[i].len);
                    ret = inet_pton(AF_INET, arg_buf, &user->nasip);
                    if (!ret) {
                        portal_del_user(user);
                        PORTAL_DEBUG("here");
                        return NULL;
                    }
                    if (!portal_find_acip_by_ip(user->nasip)) {
                        portal_del_user(user);
                        PORTAL_DEBUG("nasip no exist");
                        return NULL;
                    }
                    break;
                case PORTAL_URL_ACNAME:
                    memset(user->acname, 0, sizeof(user->acname));
                    memcpy(user->acname, uri_store[i].start, uri_store[i].len);
                    if (!(user->nasip = portal_find_acip_by_name(user->acname))) {
                        portal_del_user(user);
                        PORTAL_DEBUG("here");
                        return NULL;
                    }
                    break;
                case PORTAL_URL_SSID:
                    memcpy(user->ssid, uri_store[i].start, uri_store[i].len);
                    break;
                default:
                    break;
                }
            }
        }
    }
    if (!user->nasip) {
        portal_del_user(user);
        PORTAL_DEBUG("here");
        return NULL;
    }

    return user;
}

static bool portal_parse_authen_info(struct portal_user_s *user, char *content)
{
    char *username, *password, *cr, *bind;

    if (strncmp(content, "username=", strlen("username="))) {
        return FALSE;
    }

    if (!(password = strstr(content, "password="))) {
        return FALSE;
    }

    if ((bind = strstr(content, "bindmac=1"))) {
        *(bind - 1) = '\0';

    }
    username = strlen("username=") + content;

    *(password - 1) = '\0';
    password += strlen("password=");
    if ((cr = strchr(password, '\r'))) {
        cr = '\0';
    }
    strcpy(user->username, username);
    strcpy(user->password, password);

    if (bind) {
        portal_add_bindmac_user(user->mac, username, password);
    }

    return TRUE;
}

static bool portal_http_parse(struct portal_fd_poll_s *fd_poll, char *buf)
{
    char *http_head;
    struct portal_http_header_s header;
    char *cr;
    struct portal_user_s *user;
    char send_buf[2048];
    char ipbuf[64];

    memset(&header, 0, sizeof(header));
    http_head = header.buf = buf;
    if (!strncmp(http_head, GET, strlen(GET))) {
        header.method = HTTP_GET;
        header.uri = http_head + strlen(GET);
    } else if (!strncmp(http_head, POST, strlen(POST))) {
        header.method = HTTP_POST;
        header.uri = http_head + strlen(POST);
    } else if (!strncmp(http_head, HEAD, strlen(HEAD))) {
        header.method = HTTP_HEAD;
        header.uri = http_head + strlen(HEAD);
    } else {
        header.method = HTTP_UNKNOW;
        return FALSE;
    }
    header.user_agent = strstr(http_head, USER_AGENT);
    if (header.user_agent) {
        header.user_agent += strlen(USER_AGENT);
    } else {
        return FALSE;
    }

    header.content = strstr(http_head, CRLFCRLF);
    if (header.content) {
        header.content += strlen(CRLFCRLF);
    } else {
        return FALSE;
    }
    while (*header.uri++ == ' ');
    header.args = strstr(header.uri, "?");
    if (header.args) {
        *header.args++ = '\0';
    } else {
        return FALSE;
    }
    if (header.user_agent) {
        cr = strstr(header.user_agent, "\r\n");
        if (cr) {
            *cr = '\0';
        }
    }
    if (header.uri) {
        cr = strstr(header.uri, "\r\n");
        if (cr) {
            *cr = '\0';
        }
    }
    if (header.args) {
        cr = strstr(header.args, " ");
        if (cr) {
            *cr = '\0';
        }
    }

    if (!strncmp(header.uri, AUTHEN, strlen(AUTHEN))) {
        if (header.method != HTTP_GET
            || !(user = portal_uri_args_parse(fd_poll, header.args))) {
            return FALSE;
        }
        portal_make_sessionid(user);
        inet_ntop(AF_INET, &portal_args.portal_ip, ipbuf, sizeof(ipbuf));
        snprintf(send_buf, sizeof(send_buf), portal_result_302, ipbuf,
                 portal_args.http_port, LOGIN, user->sessionid);
        send(fd_poll->fd, send_buf, strlen(send_buf), 0);
        return FALSE;
    } else if (!strncmp(header.uri, LOGIN, strlen(LOGIN))) {
        if (header.method == HTTP_GET) {
            send(fd_poll->fd, http_head_buf, strlen(http_head_buf), 0);
            send(fd_poll->fd, portal_login_page, strlen(portal_login_page), 0);
            return FALSE;
        } else if (header.method == HTTP_POST) {
            if (strncmp(header.args, "id=", strlen("id="))) {
                send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
                return FALSE;
            }
            user = portal_find_user_by_id(header.args + strlen("id="));
            if (!user) {
                send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
                return FALSE;
            }
            if (!portal_parse_authen_info(user, header.content)) {
                send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
                return FALSE;
            }
            PORTAL_DEBUG("here, username %s, password %s", user->username, user->password);
            portal_send_msg(user, portal_args.portal_send_fd,
                            portal_args.authen_mode ? PORTAL_TYPE_REQ_AUTH : PORTAL_TYPE_REQ_CHALLENGE);
            fd_poll->auth_req = TRUE;
            return TRUE;
        } else {
            send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
            return FALSE;
        }
    } else if (!strncmp(header.uri, ONLINE, strlen(ONLINE))) {
        if (header.method == HTTP_GET) {
            send(fd_poll->fd, http_head_buf, strlen(http_head_buf), 0);
            send(fd_poll->fd, portal_online_page, strlen(portal_online_page), 0);
            return FALSE;
        } else {
            send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
            return FALSE;
        }
    } else if (!strncmp(header.uri, LOGOUT, strlen(LOGOUT))) {
        if (strncmp(header.args, "id=", strlen("id="))) {
            send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
            return FALSE;
        }
        user = portal_find_user_by_id(header.args + strlen("id="));
        if (!user) {
            send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
            return FALSE;
        }
        portal_send_msg(user, portal_args.portal_send_fd, PORTAL_TYPE_REQ_LOGOUT);
            fd_poll->auth_req = TRUE;        
    } else if (!strncmp(header.uri, OFFLINE, strlen(OFFLINE))) {
        if (header.method == HTTP_GET) {
            send(fd_poll->fd, http_head_buf, strlen(http_head_buf), 0);
            send(fd_poll->fd, portal_offline_page, strlen(portal_offline_page), 0);
            if (strncmp(header.args, "id=", strlen("id="))) {
                send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
                return FALSE;
            }
            user = portal_find_user_by_id(header.args + strlen("id="));
            if (user) {
                portal_del_user(user);
                PORTAL_DEBUG("here");
                return TRUE;
            }
            return FALSE;
        } else {
            send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
            return FALSE;
        }
    } else {
        send(fd_poll->fd, http_bad_req, strlen(http_bad_req), 0);
        return FALSE;
    }

    return TRUE;
}

int portal_get_authen_fd(struct list_head *sock_head)
{
    struct portal_fd_poll_s *fd_poll;
    
    list_for_each_entry(fd_poll, sock_head, usr_lst, struct portal_fd_poll_s) {
        if (fd_poll->auth_req) {
            return fd_poll->fd;
        }
    }

    return 0;
}

void portal_pro_http_req(int fd)
{
    struct portal_fd_poll_s *fd_poll;
    int len;
    char rx_buf[RX_BUF_SIZE] = {0};

    list_for_each_entry(fd_poll, &portal_args.hlst[fd % (FD_POLL_SIZE - 1)], hlst, struct portal_fd_poll_s) {
        if (fd_poll->fd == fd) {
            len = recv(fd, rx_buf, RX_BUF_SIZE, 0);
            if (len <= 0) {
                portal_del_listen_fd(fd);
                return;
            }
            if (!portal_http_parse(fd_poll, rx_buf)) {
                portal_del_listen_fd(fd);
            }

            return;
        }
    }
}


void portal_del_listen_fd(int fd)
{
    struct portal_fd_poll_s *fd_poll;

    list_for_each_entry(fd_poll, &portal_args.hlst[fd & (FD_POLL_SIZE - 1)],
                        hlst, struct portal_fd_poll_s) {
        if (fd_poll->fd == fd) {
            list_del(&fd_poll->lst);
            list_del(&fd_poll->hlst);
            list_del(&fd_poll->usr_lst);
            if (fd_poll->user) {
                fd_poll->user->sock_num--;
            }
            portal_args.sock_num--;
            epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_DEL, fd, NULL);
            close(fd);
            free(fd_poll);
            break;
        }
    }
}

void portal_del_user_listen_fd(struct list_head *sock_head)
{
    struct portal_fd_poll_s *fd_poll;

    list_for_each_entry(fd_poll, sock_head, usr_lst, struct portal_fd_poll_s) {
        list_del(&fd_poll->lst);
        list_del(&fd_poll->hlst);
        list_del(&fd_poll->usr_lst);
        fd_poll->user->sock_num--;
        portal_args.sock_num--;
        epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_DEL, fd_poll->fd, NULL);
        close(fd_poll->fd);
        free(fd_poll);
    }
}    

void portal_add_listen_fd(int fd)
{
    int client_fd;
    struct sockaddr_in sin;
    struct epoll_event ev;
    socklen_t len = sizeof(sin);
    struct portal_fd_poll_s *fd_poll;

    client_fd = accept(fd, (struct sockaddr *)&sin, &len);
    if (client_fd < 0) {
        PORTAL_DEBUG("accept %d failed, errno %d", fd, errno);
        return;
    }

    fd_poll = malloc(sizeof(*fd_poll));
    if (!fd_poll) {
        close(client_fd);
        return;
    }

    memset(fd_poll, 0, sizeof(*fd_poll));
    fd_poll->fd = client_fd;
    fd_poll->ts = (int)time(NULL);
    memset(&ev, 0, sizeof(ev));
    ev.events = EPOLLIN;
    ev.data.fd = client_fd;
    epoll_ctl(portal_args.epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);
    list_add_tail(&fd_poll->hlst, &portal_args.hlst[client_fd & (FD_POLL_SIZE - 1)]);
    list_add_tail(&fd_poll->lst, &portal_args.lst);
    portal_args.sock_num++;
}
